package cn.sciento.starter.social.wechat.connection;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import cn.sciento.starter.social.core.common.connect.SocialTemplate;
import cn.sciento.starter.social.core.provider.Provider;
import org.springframework.social.oauth2.AccessGrant;
import org.springframework.social.oauth2.OAuth2Parameters;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestClientException;

public class WechatTemplate extends SocialTemplate {
  private String clientId;
  
  private String clientSecret;
  
  private String accessTokenUrl;
  
  private String authorizeUrl;
  
  public WechatTemplate(Provider provider) {
    super(provider);
    setUseParametersForClientAuthentication(true);
    this.clientId = provider.getAppId();
    this.clientSecret = provider.getAppKey();
    String clientInfo = "?appid=" + formEncode(this.clientId);
    this.authorizeUrl = provider.getAuthorizeUrl() + clientInfo;
    this.accessTokenUrl = provider.getAccessTokenUrl();
  }
  
  public String buildAuthenticateUrl(OAuth2Parameters parameters) {
    StringBuilder authUrl = new StringBuilder(this.authorizeUrl);
    authUrl.append('&').append("response_type").append('=').append("code");
    for (Iterator<Map.Entry<String, List<String>>> additionalParams = parameters.entrySet().iterator(); additionalParams.hasNext(); ) {
      Map.Entry<String, List<String>> param = additionalParams.next();
      String name = formEncode(param.getKey());
      for (Iterator<String> values = ((List<String>)param.getValue()).iterator(); values.hasNext(); ) {
        authUrl.append('&').append(name);
        String value = values.next();
        if (StringUtils.hasLength(value))
          authUrl.append('=').append(formEncode(value)); 
      } 
    } 
    return authUrl.toString();
  }
  
  public AccessGrant exchangeForAccess(String authorizationCode, String redirectUri, MultiValueMap<String, String> additionalParameters) {
    LinkedMultiValueMap linkedMultiValueMap = new LinkedMultiValueMap();
    linkedMultiValueMap.set("appid", this.clientId);
    linkedMultiValueMap.set("secret", this.clientSecret);
    linkedMultiValueMap.set("code", authorizationCode);
    linkedMultiValueMap.set("grant_type", "authorization_code");
    if (additionalParameters != null)
      linkedMultiValueMap.putAll((Map)additionalParameters); 
    return postForAccessGrant(this.accessTokenUrl, (MultiValueMap<String, String>)linkedMultiValueMap);
  }
  
  public AccessGrant refreshAccess(String refreshToken, MultiValueMap<String, String> additionalParameters) {
    LinkedMultiValueMap linkedMultiValueMap = new LinkedMultiValueMap();
    linkedMultiValueMap.set("appid", this.clientId);
    linkedMultiValueMap.set("refresh_token", refreshToken);
    linkedMultiValueMap.set("grant_type", "refresh_token");
    if (additionalParameters != null)
      linkedMultiValueMap.putAll((Map)additionalParameters); 
    return postForAccessGrant(this.accessTokenUrl, (MultiValueMap<String, String>)linkedMultiValueMap);
  }
  
  protected AccessGrant postForAccessGrant(String accessTokenUrl, MultiValueMap<String, String> parameters) {
    Map<String, Object> result = (Map<String, Object>)getRestTemplate().postForObject(accessTokenUrl, parameters, Map.class);
    if (result == null)
      throw new RestClientException("access token endpoint returned empty result"); 
    String accessToken = (String)result.get("access_token");
    String scope = (String)result.get("scope");
    String refreshToken = (String)result.get("refresh_token");
    Long expireIn = Long.valueOf(String.valueOf(result.get("expires_in")));
    String openId = (String)result.get("openid");
    String unionId = (String)result.get("unionid");
    return new WechatAccessGrant(accessToken, scope, refreshToken, expireIn, openId, unionId);
  }
  
  private String formEncode(String data) {
    try {
      return URLEncoder.encode(data, "UTF-8");
    } catch (UnsupportedEncodingException ex) {
      throw new IllegalStateException(ex);
    } 
  }
}
